use crate::error::Error;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::process::Command;
use wanmesh_common::dto::ResponseCode;
use wanmesh_common::error::ResponseError;
use wanmesh_common::net::wireguard;
use wanmesh_server::dto::core::endpoint::EndpointType;

const WG_INTERFACE_NAME: &str = "wg0";
const WG_CONFIG_FILENAME: &str = "wg0.conf";
const WG_CONFIG_DIR: &str = "/opt/wanmesh-node/var/lib/endpoint/wireguard";

fn wg_config_filepath() -> PathBuf {
    Path::new(WG_CONFIG_DIR).join(WG_CONFIG_FILENAME)
}

pub struct WireguardEndpointController {
    api_client: Arc<wanmesh_server::http::client::Client>,
}

impl WireguardEndpointController {
    pub fn new(api_client: Arc<wanmesh_server::http::client::Client>) -> Self {
        Self { api_client }
    }

    pub async fn sync_clean(&self) -> Result<(), Error> {
        if !self
            .api_client
            .endpoint
            .exists(&wanmesh_server::dto::endpoint::action::ExistsRequest {
                filter: wanmesh_server::dto::core::endpoint::Filter {
                    endpoint_type: Some(EndpointType::Srv6OverWireguard),
                    ..Default::default()
                },
            })
            .await?
            .exists
        {
            log::info!("删除wireguard endpoint");
            self.delete().await?;
        }

        Ok(())
    }

    pub async fn sync(
        &self,
        endpoint: &wanmesh_server::dto::endpoint::EndpointInfo,
    ) -> Result<(), Error> {
        let span = tracing::error_span!("sync", endpoint_id = endpoint.inner.endpoint_id);
        let _guard = span.enter();

        log::info!("同步wireguard endpoint");

        log::info!("更新wireguard endpoint配置");
        let config = wireguard::config::Config::try_from(endpoint)?;
        wg_write_config(&config).await?;

        let config_filepath = wg_config_filepath();
        let config_filepath = config_filepath
            .to_str()
            .ok_or(anyhow::anyhow!("无效的配置路径：{:?}", config_filepath))?;

        if interface_exists(WG_INTERFACE_NAME).await? {
            Command::new("wg")
                .args(["syncconf", WG_INTERFACE_NAME, config_filepath])
                .output()
                .await
                .map_err(anyhow::Error::new)?;
        } else {
            self.setup(endpoint).await?;
        }

        Ok(())
    }

    pub async fn add(
        &self,
        endpoint: &wanmesh_server::dto::endpoint::EndpointInfo,
    ) -> Result<(), Error> {
        log::info!("添加wireguard endpoint");

        let config = wireguard::config::Config::try_from(endpoint)?;
        wg_write_config(&config).await?;

        self.setup(endpoint).await
    }

    async fn setup(
        &self,
        endpoint: &wanmesh_server::dto::endpoint::EndpointInfo,
    ) -> Result<(), Error> {
        if wg_interface_exists(WG_INTERFACE_NAME).await? {
            return Err(err_interface_already_exist(WG_INTERFACE_NAME));
        }

        wg_interface_add(WG_INTERFACE_NAME).await?;

        if let Some(ip) = &endpoint.inner.inner.ipv4 {
            interface_add_ipv4(WG_INTERFACE_NAME, &ip.address).await?;
        }

        if let Some(ip) = &endpoint.inner.inner.ipv6 {
            interface_add_ipv6(WG_INTERFACE_NAME, &ip.address).await?;
        }

        wg_interface_up(WG_INTERFACE_NAME).await
    }

    pub async fn delete(&self) -> Result<(), Error> {
        log::info!("删除wireguard endpoint",);

        if !interface_exists(WG_INTERFACE_NAME).await? {
            log::warn!("接口不存在，interface_name={}", WG_INTERFACE_NAME);
        } else {
            log::info!("删除接口，interface_name={}", WG_INTERFACE_NAME);
            interface_delete(WG_INTERFACE_NAME).await?;
            wg_delete_config().await?;
        }
        Ok(())
    }
}

async fn wg_write_config(config: &wireguard::config::Config) -> Result<(), Error> {
    let config_str = config.to_config_string();
    tokio::fs::write(wg_config_filepath(), config_str)
        .await
        .map_err(anyhow::Error::new)?;
    Ok(())
}

async fn wg_delete_config() -> Result<(), Error> {
    tokio::fs::remove_file(wg_config_filepath())
        .await
        .map_err(anyhow::Error::new)?;
    Ok(())
}

async fn wg_interface_exists(interface_name: &str) -> Result<bool, Error> {
    let output = Command::new("wg")
        .args(["show", interface_name])
        .output()
        .await
        .map_err(|e| Error::Other(anyhow::Error::new(e)))?;
    Ok(output.status.success())
}

async fn wg_interface_add(interface_name: &str) -> Result<(), Error> {
    let output = Command::new("ip")
        .args(["link", "add", "dev", interface_name, "type", "wireguard"])
        .output()
        .await
        .map_err(|e| Error::Other(anyhow::Error::new(e)))?;
    if !output.status.success() {
        return Err(Error::Other(anyhow::anyhow!(
            "添加接口失败，interface_name={}，{}",
            interface_name,
            String::from_utf8_lossy(&output.stderr)
        )));
    }
    Ok(())
}

async fn wg_interface_up(interface_name: &str) -> Result<(), Error> {
    let output = Command::new("ip")
        .args(["link", "set", "mtu", "1420", "up", "dev", interface_name])
        .output()
        .await
        .map_err(|e| Error::Other(anyhow::Error::new(e)))?;
    if !output.status.success() {
        return Err(Error::Other(anyhow::anyhow!(
            "启动接口失败，interface_name={}，{}",
            interface_name,
            String::from_utf8_lossy(&output.stderr)
        )));
    }
    Ok(())
}

async fn interface_exists(interface_name: &str) -> Result<bool, Error> {
    let output = Command::new("ip")
        .args(["link", "show", interface_name])
        .output()
        .await
        .map_err(|e| Error::Other(anyhow::Error::new(e)))?;
    Ok(output.status.success())
}

async fn interface_add_ipv4(interface_name: &str, ip: &cidr::Ipv4Inet) -> Result<(), Error> {
    let output = Command::new("ip")
        .args([
            "-4",
            "address",
            "add",
            "dev",
            interface_name,
            ip.to_string().as_str(),
        ])
        .output()
        .await
        .map_err(|e| Error::Other(anyhow::Error::new(e)))?;
    if !output.status.success() {
        return Err(Error::Other(anyhow::anyhow!(
            "添加IPv4地址失败，interface_name={}，{}",
            interface_name,
            String::from_utf8_lossy(&output.stderr)
        )));
    }
    Ok(())
}

async fn interface_add_ipv6(interface_name: &str, ip: &cidr::Ipv6Inet) -> Result<(), Error> {
    let output = Command::new("ip")
        .args([
            "-6",
            "address",
            "add",
            "dev",
            interface_name,
            ip.to_string().as_str(),
        ])
        .output()
        .await
        .map_err(|e| Error::Other(anyhow::Error::new(e)))?;
    if !output.status.success() {
        return Err(Error::Other(anyhow::anyhow!(
            "添加IPv6地址失败，interface_name={}，{}",
            interface_name,
            String::from_utf8_lossy(&output.stderr)
        )));
    }
    Ok(())
}

async fn interface_delete(interface_name: &str) -> Result<(), Error> {
    let output = Command::new("ip")
        .args(["link", "delete", "dev", interface_name])
        .output()
        .await
        .map_err(|e| Error::Other(anyhow::Error::new(e)))?;
    if !output.status.success() {
        return Err(Error::Other(anyhow::anyhow!(
            "删除接口失败，interface_name={}，{}",
            interface_name,
            String::from_utf8_lossy(&output.stderr)
        )));
    }
    Ok(())
}

fn err_interface_already_exist(interface_name: &str) -> Error {
    Error::ResponseError(ResponseError {
        code: ResponseCode::BAD_REQUEST,
        message: format!("接口已存在，interface_name={}", interface_name),
        backtrace: Default::default(),
    })
}
